﻿using System;
using System.Collections;

namespace Genko.Common.Collections
{
	/// <summary>
	/// Represents a hashtable that keeps track of the insertion order.
	/// </summary>
	public class HashList : ArrayList, IDictionary
	{
		protected Hashtable table;

		public HashList() : base()
		{
			this.table = new Hashtable();
		}

		public HashList(int capacity) : base(capacity)
		{
			this.table = new Hashtable();
		}

		public HashList(ICollection c) : base(c)
		{
			this.table = new Hashtable();
		}

		public virtual ICollection Keys
		{
			get {
				return this.table.Keys;
			}	
		}

		protected string CreateDefaultKeyFor(object o)
		{
			return Guid.NewGuid().ToString();
		}

		protected virtual void ShiftDownFrom(int index)
		{
			object key;
			int pos = 0;
			for(int i = index; i < this.Count; i++)
			{
				key = KeyOfIndex(i);
				pos = (int) this.table[key] - 1;
				this.table[key] = pos;
			}
		}

		protected virtual void ShiftUpFrom(int index)
		{
			object key;
			int pos = 0;
			for(int i = this.Count - 1; i >= index; i--)
			{
				key = KeyOfIndex(i);
				pos = (int) this.table[key] + 1;
				this.table[key] = pos;
			}
		}

		public override int Add(object value)
		{
			return this.Add(CreateDefaultKeyFor(value), value);
		}

		public override void Insert(int index, object value)
		{
			Insert (CreateDefaultKeyFor(value), index, value);
		}

		public override void RemoveAt(int index)
		{
			object key = KeyOfIndex(index);
			if (key != null) {
				ShiftDownFrom(index + 1);
				this.table.Remove(key);
				base.RemoveAt (index);
			}
		}

		public override void Remove(object obj)
		{
			bool unmatch = true;
			for (int i = 0; i < this.Count && unmatch; i++) {
				if (this[i].Equals(obj)) {
					RemoveAt(i);
					unmatch = false;
				}
			}
		}

		public override void Clear()
		{
			base.Clear();
			this.table.Clear();
		}

		public override object Clone()
		{
			HashList list = new HashList(this.Count);
			for (int i = 0; i < this.Count; i++) 
			{
				list.Add(KeyOfIndex(i), this[i]);
			}
			return list;
		}

		public override void Reverse()
		{
			this.Reverse (0, this.Count);
		}

		public override void Reverse(int index, int count)
		{
			base.Reverse (index, count);
			int mid = (index + count) / 2;
			int last = (index + count) - 1;
			object keyLeft;
			object keyRight;
			for (int i = 0, j = last; i < mid; i++, j--) {
				keyLeft = KeyOfIndex(i);
				keyRight = KeyOfIndex(j);
				this.table[keyLeft] = j;
				this.table[keyRight] = i;
			}
			
		}

		public override void RemoveRange(int index, int count)
		{
			throw new NotSupportedException();
		}

		public override void InsertRange(int index, ICollection c)
		{
			throw new NotSupportedException();
		}

		public virtual int Add(string key, object value)
		{
			if (ContainsKey(key))
			{
				int index = IndexOfKey(key);
				this[index] = value;
				return index;
			} 
			else 
			{
				this.table.Add(key, base.Count);
				return base.Add(value);
			}
		}

		public virtual int IndexOfKey(object key)
		{
			return ContainsKey(key) ? ((int) this.table[key]) : -1;
		}

		public virtual object KeyOfIndex(int index)
		{
			int i = 0;
			foreach(object key in this.table.Keys)
			{
				i = (int) this.table[key];
				if (index == i) {
					return key;
				}
			}
			return null;
		}

		public virtual bool ContainsKey(object key)
		{
			return this.table.ContainsKey(key);
		}

		public virtual bool RemoveForKey(object key)
		{
			if (ContainsKey(key)) {
				int pos = (int) this.table[key];
				ShiftDownFrom(pos + 1);
				base.RemoveAt(pos);
				this.table.Remove(key);
				return true;
			} else {
				return false;
			}
		}

		public virtual void Insert(object key, int index, object val)
		{
			if (index < 0)
				throw new ArgumentOutOfRangeException(index + " < 0");
			if (index > this.Count)
				throw new ArgumentOutOfRangeException(index + " > " + this.Count);
			ShiftUpFrom(index);
			base.Insert(index, val);
			this.table[key] = index;
		}

		public virtual void Move(int fromIndex, int toIndex)
		{
			if(fromIndex != toIndex)
			{
				object key = KeyOfIndex(fromIndex);
				object e = this[fromIndex];
				RemoveAt(fromIndex);
				Insert(key, toIndex, e);
			}
		}

		public object ReplaceKey(object newKey, object oldKey)
		{
			if (ContainsKey(oldKey))
				return ReplaceKeyAt(newKey, IndexOfKey(oldKey));
			else
				return null;
		}

		public object ReplaceKeyAt(object key, int index)
		{
			if (index < 0)
				throw new ArgumentOutOfRangeException(index + " < 0");
			if (index >= this.Count)
				throw new ArgumentOutOfRangeException(index + " >= " + this.Count);
			object previousKey = KeyOfIndex(index);
			if (ContainsKey(key)) {
				if(previousKey.Equals(key)) {
					return previousKey;
				} else {
					throw new ArgumentException(
						"Can't set key to index because the key already exists in other index");
				}
			} else {
				this.table.Remove(previousKey);
				this.table.Add(key, index);
				return previousKey;
			}
		}

		public virtual object this[object key]
		{
			get {
				if (key is int) {
					return base[(int)key];
				}
				if (ContainsKey(key)) {
					return this[(int)this.table[key]];
				} else {
					return null;
				}
			}
			set {
				if (key is int) {
					base[(int)key] = value;
				} else {
					this.Add(key, value);
				}
			}
		}

		public virtual void Add(object key, object value)
		{
			if (ContainsKey(key))
			{
				int index = IndexOfKey(key);
				this[index] = value;
			} 
			else 
			{
				this.table.Add(key, base.Count);
				base.Add(value);
			}
		}

		public ICollection Values
		{
			get { return this; }
		}

		public new IDictionaryEnumerator GetEnumerator()
		{
			return new HashListEnumerator(this);
		}
	}

	internal class HashListEnumerator : IDictionaryEnumerator
	{
		private HashList hashList;
		private int index;

		public HashListEnumerator(HashList list)
		{
			hashList = list;
			Reset();
		}

		public object Current
		{
			get { return hashList[index]; }
		}

		public bool MoveNext()
		{
			index++;
			return index < hashList.Count;
		}

		public void Reset()
		{
			index = -1;
		}

		public DictionaryEntry Entry
		{
			get { return new DictionaryEntry(hashList.KeyOfIndex(index), hashList[index]); }
		}

		public object Key
		{
			get { return hashList.KeyOfIndex(index); }
		}

		public object Value
		{
			get { return hashList[index]; }
		}
	}
}
